package spynx.desl.runtime.impl;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import spynx.desl.runtime.IInvocator;

public class Invocator implements IInvocator {

	private Object host;
	private String methodName;
	
	private static Map<String, Class<?>> typeMap;
	
	static {
		typeMap = new HashMap<String, Class<?>>();
		typeMap.put("int", Integer.class);
		typeMap.put("boolean", Boolean.class);
	}
	
	public Invocator(Object host, String name) {
		this.host = host;
		this.methodName = name;
	}
	
	Class<?> resolveClass() {
		return host instanceof Class<?> ? (Class<?>)host : host.getClass();
	}
	
	@Override
	public Object invoke(Object... args) {
		Method method = null;
		int matchRank = -1;
		
		for(Method m: resolveClass().getMethods())
			if (m.getName().equals(methodName)) {
				Class<?>[] paramTypes = m.getParameterTypes();
				int rank = matchParameters(paramTypes, args);
				if (rank > matchRank || method == null) {
					matchRank = rank;
					method = m;
				}
			}
		
		if (method != null)
			try {
				return method.invoke(host, args);
			} catch (Exception e) {
				throw new RuntimeException("Error invoke " + toString(), e);
			} 
		
		throw new RuntimeException(String.format("%s not found.", toString()));
	}

	private int matchParameters(Class<?>[] paramTypes, Object[] args) {
		int rank = 0;
		if (args.length == paramTypes.length) {
			for(int i = 0; i < args.length; i++)
				if (isInstanceOf(paramTypes[i],args[i])) rank++;
				else if (args[i] != null && !paramTypes[i].isAssignableFrom(args[i].getClass()))
					return -1;
		}
		else 
			rank = -1;
		return rank;
	}
	
	private boolean isInstanceOf(Class<?> param, Object arg) {
		param = typeMap.containsKey(param.getName()) ? typeMap.get(param.getName()) : param;
		return param.isInstance(arg);
	}

	@Override
	public String toString() {
		Class<?> cls = resolveClass();
		return cls.getName() + "." + methodName;
	}
}
